import csv
import re
import socket
import os
import sys


class GetInput(object):
    def __init__(self):
        self.qdict = {}
        self.type_dict = {'binary': self._binary,
                          'file': self._fname,
                          'multi': self._multi,
                          'text': self._text
                          }

    def add(self, question, answer):
        self.qdict[question] = answer

    def ask(self, question, atype, akey=None):
        """ Takes a question to ask and response type.  Returns an answer. """
        self.question = question
        self.atype = atype
        self.akey = akey
        try:
            print ''
            print question
            answer = raw_input('>')
        except KeyboardInterrupt:
            print 'Input not recognized. Input valid response or type \'q\' to quit.'
            self.ask(self.question, self.atype, self.akey)
        else:
            if answer == 'q': sys.exit()
            if self.akey and answer not in self.akey:
                print 'Response not in accepted answer list.'
                self.ask(self.question, self.type, self.akey)
            self.qdict[question] = answer
            return (self.type_dict[atype](answer, akey))

    def _multi(self, answer, akey):
        """ Ask the user a multiple choice question. """
        if answer not in akey:
            print 'Response is not acceptable.'
            print ''
            return self.ask(self.question, 'multi', akey)
        return answer


    def _binary(self, answer, akey=None):
        """ Ask the user a question with a yes or no response.
            Store the question and answer in self.qdict. """
        pos = ['Y', 'y', 'YES', 'yes', 'Yes']
        neg = ['N', 'n', 'NO', 'No', 'no']
        if answer in pos: return True
        elif answer in neg: return False
        else:
            print 'Response not in accepted answer list.'
            print ''
            return self.ask(self.question, 'binary')

    def _fname(self, answer, akey=None):
        """ Ask the user a question where the answer is a filename.
            Verify the filename exists.
            Store the question and answer in self.qdict. """
        if '.csv' not in answer:
            print 'Only CSV files are accepted.'
            print ''
            return self.ask(self.question, 'file')
        elif not os.path.exists(answer):
            print 'File not found.'
            print ''
            return self.ask(self.question, 'file')
        else: return answer

    def _text(self, answer, akey=None):
        """ Return raw text. """
        return answer

class getNet(object):
    def __init__(self):
        self.hosts = []

    def hostfile(self, filename=None):
        """ Parse host file for targets. """
        if filename == None:
            filename = responses.ask('Enter filename:', 'file')
        print 'Parsing {} now.'.format(str(filename).upper())
        with open(filename, 'rb') as f:
            parse = csv.reader(f)
            for line in parse:
                try:
                    self.hosts.append((line[0], line[1]))
                except IndexError:
                    break
        print 'Complete.'
        return self.hosts

    def netscan(self):
        """ Scan the network for targets using nmap.
            If multiple interfaces exist on the local machine you choose which to use. """
        f = open('acquired_hosts.csv', 'wb')
        csvout = csv.writer(f)
        host = socket.gethostname()
        ips = [ip for ip in socket.gethostbyname_ex(host)[2]
               if not ip.startswith('127.')]
        networks = {str(i + 1):'%s.%s.%s.0/24' % re.search(r'(\d+)\.(\d+)\.(\d+)\.\d+', ips[i]).groups()
                    for i in range(len(ips))}
        if len(networks) > 1:
            print 'The following networks were found:'
            print '\n'.join(['%s. %s' % (k,v) for k,v in networks.items()])
            choice = responses.ask('Please choose a network', 'multi', [str(i) for i in range(len(networks.items()))])
            if choice in networks.keys():
                netscan = networks[choice]
            elif choice == '':
                netscan = networks['1']
            else:
                print 'Choice out of range.'
                sys.exit()
        elif len(networks) == 1:
            netscan = networks['1']
            print '%s network found, setting as scan target.\n' % netscan
        else:
            print 'No networks found.  Quitting.'
            sys.exit()
        try:
            import nmap
            nm = nmap.PortScanner()
        except WindowsError:
            print 'Unable to initialize nmap.  Please check installation.'
            sys.exit()
        nscan = nm.scan(hosts=netscan, arguments='-sS -p 135')
        self.hosts = [(nscan['scan'][s]['hostname'], s) for s in nscan['scan']
                      if 'open' in nscan['scan'][s]['tcp'][135]['state']]
        for host in self.hosts:
            csvout.writerow(host)
        f.close()
        return self.hosts

    def build_hosts(self):
        """ Create a target file by entering ip/hostname pairs. """
        saveit = responses.ask('Save to disk? (y/n)', 'binary')
        while True:
            hname = responses.ask('Enter host name, leave blank and press Enter if done.', 'text')
            if hname == '':
                break
            else:
                ip = responses.ask('Enter IP address, if known, or leave blank and press Enter.', 'text')
            self.hosts.append((hname, ip))
            print hname, ip
        print ''
        print self.hosts
        print 'Host list built.'
        if saveit == True:
            with open('build_hosts.csv', 'wb') as f:
                csv.writer(f).writerows(self.hosts)
        return self.hosts
    
    def get_ip(self, host):
        return socket.gethostbyname(host)
    
    
def prep_hosts():
    """ Check if scan_summary.csv exists.  If it does, ask to update instead of a new scan. """
    if os.path.exists('scan_summary.csv'):
        if raw_input('Previous scan found, refresh results? (y/n)\n>') in ['y', 'Y']:
            host_targets = getNet()
            hosts = host_targets.hostfile('scan_summary.csv')[1:]
            print 'Hosts from file:\n', '\n'.join([str(host) for host in hosts])
            print ''
            with open('scan_summary.csv', 'rb') as f:
                oldscan = [line for line in csv.reader(f)][1:]
            if raw_input('Scan network for new hosts? (y/n)/n') in ['y', 'Y']:
                hosts_fromscan = host_targets.netscan()
                print 'Hosts from scan:\n', '\n'.join([str(host) for host in hosts_fromscan])
                hosts.extend([host for host in hosts_fromscan if host[0] not in [h[0] for h in hosts]])
            return hosts, oldscan, True
    hosts = get_hosts()
    return hosts, None, False

def get_hosts():
    """ If no previous summary exists, or if additional hosts are desired, gather hosts. """
    c = responses.ask('''Please choose from one of the following options:\n
    \t1. Load targets from CSV.
    \t2. Scan network with NMAP.
    \t3. Enter single target name or IP.
    \t4. Build hosts list.''', 'multi', ['1', '2', '3', '4'])
    hclass = getNet()
    if c == '3':
        hinput = responses.ask('Enter IP address or hostname to scan.', 'text')
        return ([(hinput, '')] if re.match(r'^\d+\.\d+\.\d+\.\d+', hinput) else
                [('', hinput)])
    if c == '1': return hclass.hostfile()
    elif c == '2': return hclass.netscan()
    elif c == '4': return hclass.build_hosts()
    else:
        print "Input not recognized."
        print ""
        get_hosts()

    


global responses
responses = GetInput()
